<template>
    <canvas ref="canvas" width="100" height="100" class="p-painter-paths"></canvas>
</template>

<script>
// import _ from 'lodash';
import { mapState } from 'vuex';
import bus from '../utils/Eventbus.js';
import { PATH_SIGNS } from '../core/SYSTEM.enum.js';
import Point from '../utils/Point';
import BlobHelper from '../utils/v-blob.js';
import * as Chef from '../core/Chef.js';
import * as enums from '../store/status-enums.js';
import * as Utils from '../core/utils';
import * as Matrix from '../utils/Matrix.js';
import * as Painter from '../core/Painter.js';
import * as Graph from '../utils/Graph/index.js';
import _ from 'lodash';
// import BlobHelper from './v-blob.js';
// import * as Utils from './Utils.js';
// import * as enums from './status-enums.js';

/**
 * p-painter-paths
 * @author wangdasong
 * @date 2023.01.30
 */
export default {
    name: 'p-painter-paths',
    components: {},
    props: {
        // display mode of the map, can be '2d' or 'isometric', default is '', means '2d', maybe '3d' in the future.
        mode: {
            type: String,
            default: ''
        },
        value: Array, // local offline data
        map: Object, // map arguments
        offline: Boolean,
        scalingable: {
            type: Boolean,
            default: true
        },
        draggable: {
            type: Boolean,
            default: true
        },
        hidePaths: {
            type: Boolean,
            default: false
        }
    },
    data() {
        return {
            trueName: this.$options.name,
            matrix: null,
            ctx: null,
            ratio: null,
            dimensions: null, // [-10000, 10000, -10000, 10000],
            timer: null,
            refreshing: false,
            local: {}, // local drawing before submit ...
            localList: [],
            currentBezier: { id: '', points: [] }
        };
    },
    computed: {
        ...mapState({
            selectedPaths: state => state.businessStatus.selectedPaths,
            selectedVehicles: state => state.businessStatus.selectedVehicles,
            isDrawPathsActive: state => state.businessStatus.isDrawPathsActive,
            isMultiSelectActive: state => state.businessStatus.isMultiSelectActive,
            isAltFnActive: state => state.businessStatus.isAltFnActive,
            isDrawBezier: state => state.businessStatus.isDrawBezier
        })
        // isometric() {
        //     return this.mode === 'isometric';
        // }
    },
    watch: {
        isDrawPathsActive(val) {
            if (!val && this.localList && this.localList.length) this.$emit('drawPaths', this.localList);
        },
        'currentBezier.points': {
            handler() {
                this.$emit('bezierChanged', this.currentBezier);
            },
            deep: true
        }
        // hidePaths(val) {
        //     console.log('p-painter-paths.watch.hidePaths:', val);
        //     this.setCtxCache('');
        // }
    },
    created() {
        bus.on(`${this.trueName}.refresh`, this.refresh);
        bus.on(`${this.trueName}.locatePath`, this.locatePath);
        bus.on(`${this.trueName}.undo`, this.undo);
        bus.on(`${this.trueName}.editChanged`, this.editChanged);
        bus.on(`${this.trueName}.clearLocals`, this.clearLocals);
    },
    beforeDestroy: function() {
        if (this.timer) cancelAnimationFrame(this.timer);
        // 清除事件监听
        bus.off(`${this.trueName}.refresh`, this.refresh);
        bus.off(`${this.trueName}.locatePath`, this.locatePath);
        bus.off(`${this.trueName}.undo`, this.undo);
        bus.off(`${this.trueName}.editChanged`, this.editChanged);
        bus.off(`${this.trueName}.clearLocals`, this.clearLocals);
    },
    mounted() {
        this.init();
    },
    methods: {
        init() {
            this.paths = [];
            this.pathsLabels = [];
            this.pathsSigns = [];
            console.log('p-painter-paths.init: ', this._uid, this.dimensions);
            this.initCanvas();
        },
        initCanvas() {
            // console.log('paths.initCanvas: ', this._uid, this.map.width, this.$parent.$el.clientWidth);
            let obj = this.$refs.canvas;
            if (!this.ctx) this.ctx = obj.getContext('2d');
            if (!this.ratio) this.ratio = Matrix.getPixelRatio(this.ctx);
            let w = parseInt(this.$parent.$el.clientWidth, 10),
                h = parseInt(this.$parent.$el.clientHeight, 10),
                mw = window.document.body.offsetWidth,
                mh = window.document.body.offsetHeight;
            w = Math.min(w, mw) * this.ratio;
            h = Math.min(h, mh) * this.ratio;
            if (obj.width === w && obj.height === h) return;
            obj.setAttribute('width', w);
            obj.setAttribute('height', h);
        },
        initCanvasCtx(ctx = this.ctx) {
            // Isometric logic.
            let cp = new Point(this.map.originX, this.map.originY);
            // if (this.isometric) cp = Point.getIsometricPos(cp.x, cp.y);
            ctx.translate(cp.x, cp.y);
            ctx.scale(this.map.scaling, this.map.scaling);
            ctx.rotate(this.map.rotation * (Math.PI / 180));
            // console.log('p-painter-paths.initCanvasCtx: ', cp);
        },

        repaint(recalculate = false) {
            console.log('p-painter-paths.repaint: ');
            if (recalculate) {
                //
            } else {
                this.dimensions = null;
            }
            this.init();
        },
        clearLocals() {
            this.local = {};
            this.localList = [];
            this.bezier = null;
        },

        isPointInStroke(cusPos) {
            let rtn = null;
            if (!cusPos) return;
            this.refreshing = true;
            // console.log('p-painter-paths.isPointInStroke: ', cusPos);
            // if (!this.offCvsCtx) this.offscreenRender();
            // this.offscreenRender();

            this.ctx.save();
            this.initCanvasCtx();
            let paths = _.cloneDeep(this.paths);
            /*
             * Q: controlPoints of undefined
             * A: paths is undefined
             *              -- mod by ouyang on 2020.07.21
             */
            if (!paths || !paths.length) return;

            if ((!this.lastSelectedIdx && this.lastSelectedIdx !== 0) || this.lastSelectedIdx < 0)
                this.lastSelectedIdx = paths.length - 1;
            if (this.isAltFnActive) {
                this.lastSelectedIdx = paths.length - 1;
            }
            for (let i = this.lastSelectedIdx; i >= 0; i--) {
                /*
                 * Q: controlPoints of undefined
                 * A: if controlPoints is undefined or [], Don't draw path in painter
                 *              -- mod by ouyang on 2020.08.14
                 */
                if (paths[i] && paths[i].controlPoints && paths[i].controlPoints.length)
                    Painter.drawPath(this.ctx, paths[i].controlPoints, paths[i].width, paths[i].color);
                if (cusPos && this.ctx.isPointInStroke(cusPos.x, cusPos.y)) {
                    this.lastSelectedIdx = i - 1;
                    rtn = paths[i];
                    break;
                }
            }
            this.ctx.restore();
            if (!rtn) this.lastSelectedIdx = null;
            this.refreshing = false;
            return rtn;
        },
        refresh(updatedTag) {
            if (this.refreshing || !this.ctx) return;
            if (!this.$root.resources.paths) return;
            // if (this.hidePaths) {
            //     this.ctx.clearRect(0, 0, this.$refs.canvas.width, this.$refs.canvas.height);
            //     this.setCtxCache('');
            //     return;
            // }

            this.paths = this.$root.resources.paths;
            this.pathsLabels = this.$root.resources.pathsLabels;
            this.pathsSigns = this.$root.resources.pathsSigns;

            // console.log('p-painter-paths.refresh: ', this.refreshing, this.map.originX);
            this.refreshing = true;
            this.initCanvas();
            // this.initCanvasCtx();

            // console.log('p-painter-paths.refresh: ');

            // Isometric logic.
            let cp = new Point(this.map.originX, this.map.originY);
            // if (this.isometric) cp = Point.getIsometricPos(cp.x, cp.y);

            // Offscreen Render. 
            let key = `${this.map.width}_${this.map.height}_${cp.x}_${cp.y}_${this.map.scaling}_${this.map.rotation}_${this.selectedPaths}_${this.localList.length}`,
                img = this.getCtxCache(key);

            // refresh when the local points not empty
            if (this.localList.length) updatedTag = true;

            // if no move && no updates, then is't refresh after some times.
            if (img && !updatedTag && this.delayRenderNum > 10) {
                this.dynamicRender();
                // console.log('p-painter-paths.refresh:---------------0');
                return (this.refreshing = false);
            }

            // console.log('-------_>', img, updatedTag);
            if (!img || updatedTag) {
                this.delayRenderNum = 0;
                img = this.offscreenRender(key);
            }
            // console.log('p-painter-paths.refresh:---------------1');
            this.ctx.clearRect(0, 0, this.$refs.canvas.width, this.$refs.canvas.height);
            if (!this.$refs.canvas || !this.$refs.canvas.width) {
                // console.log('p-painter-paths.refresh:---------------2');
                return (this.refreshing = false);
            }
            this.ctx.drawImage(img, 0, 0);
            this.dynamicRender();
            this.delayRenderNum++; // means refresh will continue for a while.

            this.refreshing = false;
            // console.log(
            //     'p-painter-paths.refresh:',
            //     this.hidePaths,
            //     this.refreshing,
            //     !this.ctx,
            //     !this.$root.resources.paths
            // );
        },
        offscreenRender(key) {
            // console.log('p-painter-paths.offscreenRender: ');
            if (!this.offCvs) this.offCvs = document.createElement('canvas');
            if (this.offCvs.width !== this.$refs.canvas.width)
                this.offCvs.setAttribute('width', this.$refs.canvas.width);
            if (this.offCvs.height !== this.$refs.canvas.height)
                this.offCvs.setAttribute('height', this.$refs.canvas.height);
            this.offCvsCtx = this.offCvs.getContext('2d');
            this.offCvsCtx.clearRect(0, 0, this.$refs.canvas.width, this.$refs.canvas.height);
            this.offCvsCtx.save();

            // Isometric logic.
            let cp = new Point(this.map.originX, this.map.originY);
            // if (this.isometric) cp = Point.getIsometricPos(cp.x, cp.y);
            this.offCvsCtx.translate(cp.x, cp.y);
            this.offCvsCtx.scale(this.map.scaling, this.map.scaling);
            this.offCvsCtx.rotate(this.map.rotation * (Math.PI / 180));
            this.offCvsCtx.lineCap = 'round';

            // this.offCvsCtx.lineWidth = 10;
            this.offCvsCtx.strokeStyle = 'rgba(0, 0, 0, 0.5)';

            let resources = (this.value ? this.value : this.paths) || [];
            (resources || []).forEach(o => {
                this.drawPath(this.offCvsCtx, o);
                // // 选中效果
                // this.drawSelected(this.offCvsCtx, o);
                // this.drawBridges(o);
            });
            this.drawBridges(this.offCvsCtx, resources);
            this.drawLabels(this.offCvsCtx, resources);
            if (!this.value) {
                this.drawSigns(this.offCvsCtx);
                this.drawLocals(this.offCvsCtx);
            }

            this.offCvsCtx.restore();

            // set canvas cache. 
            this.setCtxCache(key, this.offCvs);

            return this.offCvs;
        },
        dynamicRender() {
            // console.log('p-painter-paths.dynamicRender: ');
            this.ctx.save();
            this.initCanvasCtx();
            let resources = (this.value ? this.value : this.paths) || [];
            (resources || []).forEach(o => {
                // 选中效果
                this.drawSelected(this.ctx, o);
            });
            this.drawLocals(this.ctx);
            this.ctx.restore();
        },
        drawPath(ctx, o, dashTag) {
            //eslint-disable-line
            // if (this.timer) cancelAnimationFrame(this.timer);
            ctx.save();
            // Isometric logic. 
            let ps = o.controlPoints;
            // if (this.isometric) ps = o.iControlPointsExt;
            Painter.drawPath(ctx, ps, o.width, o.color);
            ctx.stroke();

            // 选中效果
            if (dashTag) {
                if (this.timer) cancelAnimationFrame(this.timer);
                ctx.strokeStyle = o.strokeExt;
                ctx.lineCap = 'butt';
                ctx.setLineDash([o.width]);
                this.timer = requestAnimationFrame(() => {
                    this.pathMarch.call(this, o);
                });
                ctx.lineDashOffset = -this.offset;
                ctx.stroke();
                // // Create an editable Bezier.
                // this.drawBezier(o, dashTag);
            }
            // else if (this.timer) cancelAnimationFrame(this.timer);
            // ctx.stroke();
            // Painter.drawPoints(ctx, o.controlPoints, this.map.scaling);
            ctx.restore();

            // Create an editable Bezier.
            // console.error(this.selectedPaths);
            if (
                dashTag &&
                this.isDrawBezier &&
                this.editStatus &&
                o.id &&
                this.selectedPaths.length === 1 &&
                this.selectedPaths[0] !== '-1'
            )
                this.drawBezier(o, dashTag);
        },
        // Create an editable Bezier. 
        drawBezier(o, dashTag) {//eslint-disable-line
            if (!o || !o.controlPoints || o.controlPoints.length !== 4) return;
            // console.log('---000---> ', dashTag, this.bezier, o.id);
            // if (!dashTag && this.bezier) this.bezier = null;
            // else if (dashTag && this.bezier && o.id !== this.bezier.name) this.bezier = null;
            if (this.bezier && parseInt(o.id) !== parseInt(this.bezier.name)) {
                this.bezier = null;
                this.currentBezier = {};
            }
            if (!this.bezier) {
                this.bezier = new Graph.Bezier({
                    ctx: this.ctx,
                    ctxWidth: this.$refs.canvas.width,
                    ctxHeight: this.$refs.canvas.height,
                    ratio: this.ratio,
                    pos: o.controlPoints,
                    scaling: this.map.scaling,
                    lineWidth: o.width / 2
                });
                this.bezier.name = o.id;
                this.bezier.points = o.controlPoints.slice();
                this.currentBezier.id = o.id;
            }
            this.bezier.draw();
            this.bezier.drawPoints();
            // this.bezier.drawController();
            this.delayRenderNum = 0; // means refresh will continue for a while.
        },
        drawBridges(ctx, resources) {
            ctx.save();
            resources.forEach(o => {
                // let props = this.isometric ? 'iBridgesExt' : 'bridges';
                // Painter.drawPathBridges(ctx, o[props], o.width, o.color);
                Painter.drawPathBridges(ctx, o.bridges, o.width, o.color);
            });
            // Painter.drawPathBridges(ctx, o.controlPoints, o.width);
            ctx.restore();
        },
        drawLabels(ctx, resources) {
            resources.forEach(o => {
                if (!o.label) return;
                let p = (o.controlPoints && o.controlPoints.length && o.controlPoints[0]) || null;
                if (!p) return;
                let labs = (this.$root.resources && this.pathsLabels) || {},
                    l = labs[o.id] || null;
                // console.log('-----> ', labs, l);
                if (!l) return;
                // if (this.isometric) {
                //     l.x = l.ix;
                //     l.y = l.iy;
                // }
                ctx.save();
                Painter.drawPathLabel(ctx, l, o.color);
                ctx.restore();
            });
        },
        drawSigns(ctx) {
            let signs = (this.$root.resources && this.pathsSigns) || {};
            Object.keys(signs).forEach(k => {
                // if (signs[k].pathIdExt === '18') console.log('----> ', signs[k]);
                let s = signs[k];
                let o = this.paths.find(v => v.id === s.pathIdExt);
                if (!o) return;
                let pos = this.getPositionByProgress(o.id, s.progressExt);

                ctx.save();

                // draw signs
                if (s.typeExt === PATH_SIGNS.CLOSURE) Painter.drawSignClosure(ctx, pos.x, -pos.y, s.widthExt);
                else if (s.typeExt === PATH_SIGNS.CONDITION)
                    Painter.drawSignCondition(ctx, s.textExt, pos.x, -pos.y, s.widthExt);
                else if (s.typeExt === PATH_SIGNS.RELATIVESPEED)
                    Painter.drawSignRelativeSpeed(ctx, s.textExt, pos.x, -pos.y, s.widthExt);
                else if (s.typeExt === PATH_SIGNS.STOP) Painter.drawSignStop(ctx, pos.x, -pos.y, s.widthExt);
                else if (s.typeExt === PATH_SIGNS.SPOTTURN) Painter.drawSignSpotTurn(ctx, pos.x, -pos.y, s.widthExt);
                else if (s.typeExt === PATH_SIGNS.PROHIBITION)
                    Painter.drawSignProhibition(ctx, s.textExt, pos.x, -pos.y, s.widthExt);
                else if (s.typeExt === PATH_SIGNS.PERMISSION)
                    Painter.drawSignPermission(ctx, s.textExt, pos.x, -pos.y, s.widthExt);
                else if (s.typeExt === PATH_SIGNS.BRIDGE) Painter.drawSignBridge(ctx, pos.x, -pos.y, pos.h, s.widthExt);

                ctx.restore();
            });
        },
        drawLocals(ctx) {
            if (this.localList && this.localList.length) this.localList.forEach(o => this.drawPath(ctx, o, true));
        },
        locatePath(id) {
            if (!id) return;
            let o = this.paths.find(v => v.id === id),
                d = Matrix.getPathDimensions([o], null, this.ratio),
                zoomed = [d[0] - 5000, d[1] + 5000, d[2] - 5000, d[3] + 5000];
            console.log('p-painter-paths.locatePath: ', id, o, d, zoomed);
            this.$emit('dimensionsChange', zoomed);
        },

        getPositionByProgress(id, progress) {
            let hid = id;
            // if (this.isometric) hid += '_isometric';
            let p = BlobHelper.getPathCacheBySvgPath(hid);
            if (!p) {
                let o = this.paths.find(v => v.id === id);

                // let prop = this.isometric ? 'iPathDExt' : 'pathDExt';
                // if (o && o[prop]) p = BlobHelper.setPathCacheBySvgPath(hid, o[prop]);
                if (o && o.pathDExt) p = BlobHelper.setPathCacheBySvgPath(hid, o.pathDExt);
            }
            let len = p.getTotalLength(),
                dis = (len * progress) / 100,
                po = p.getPointAtLength(dis);
            return { x: po.x, y: po.y };
        },

        pathMarch(o) {
            if (!this.offset || this.offset > o.width * 2) this.offset = 0;
            this.offset += o.width / 10;
            // console.log(this.offset);
        },
        drawSelected(ctx, o) {
            let currentJobPaths = [];
            if (this.selectedVehicles && this.selectedVehicles.length > 0) {
                currentJobPaths = Chef.getVehicleCurrentRoutesById(
                    this.selectedVehicles[this.selectedVehicles.length - 1],
                    this.$root.resources.vehicles
                );
            }

            if (this.selectedPaths && this.selectedPaths.length && this.selectedPaths.indexOf(o.id) >= 0) {
                this.delayRenderNum = 0; // means refresh will continue for a while. 
                // console.log('p-painter-paths.drawSelected: selectedPaths -> ', this.selectedPaths);
                this.drawPath(ctx, o, true);
            } else if (currentJobPaths && currentJobPaths.indexOf(parseInt(o.id)) >= 0) {
                // console.log('p-painter-paths.drawSelected: currentJobPaths -> ', currentJobPaths);
                this.delayRenderNum = 0; // means refresh will continue for a while. 
                this.drawPath(ctx, o, true);
            }
        },
        // 通过svg的path对象，可获取线段长度、及指定位置的坐标
        customizePath(path, func) {
            const pathElement = document.createElementNS('http://www.w3.org/2000/svg', 'path');
            pathElement.setAttributeNS(null, 'd', path);
            // const length = pathElement.getTotalLength();
            // const duration = 1000;
            // const interval = length / duration;
            // let time = 0,
            //     step = 0;
            // const timer = setInterval(function() {
            //     if (time <= duration) {
            //         const x = parseInt(pathElement.getPointAtLength(step).x);
            //         const y = parseInt(pathElement.getPointAtLength(step).y);
            //         // console.log('--> ', x, y);
            //         func(x, y);
            //         step++;
            //     } else {
            //         clearInterval(timer);
            //     }
            // }, interval);
            const x = parseInt(pathElement.getPointAtLength(0).x);
            const y = parseInt(pathElement.getPointAtLength(0).y);
            // console.log('--> ', x, y);
            func(x, y);
        },

        // pointer events
        pointerDownFunc(cursorPos, transPos) {
            // Create an editable Bezier.
            // console.log('pointerFunc --> down: ', cursorPos, transPos, this.local);
            // console.log('p-painter-paths.pointerDownFunc: ');
            // for multi select ...
            if (!this.isDrawPathsActive && this.bezier) {
                this.bezierPointIdx = this.bezier.isInPath(transPos, this.map.scaling);
            } else if (this.isMultiSelectActive) {
                this.startPoint = transPos;
            } else if (this.isDrawPathsActive) {
                this.startPoint = transPos;
                this.painting = true;
            }
        },
        pointerMoveFunc(cursorPos, transPos) {
            if (this.pointerButton === 2) return;
            // console.log('pointerFunc --> move: ', cursorPos, transPos, this.local);

            // Create an editable Bezier.
            // Only control points can be draged.
            if (!this.isDrawPathsActive && this.bezier && (this.bezierPointIdx === 1 || this.bezierPointIdx === 2)) {
                this.bezier.update(this.bezierPointIdx, transPos);
                // this.currentBezier.points = this.bezier.points.concat();
                // this.currentBezier.points = _.cloneDeep(this.bezier.points);
                this.currentBezier.points = JSON.parse(JSON.stringify(this.bezier.points));
                return;
            } else if (this.isDrawPathsActive) {
                // if (!this.isDrawPathsActive) return;

                if (!this.painting) return;
                // console.log('p-painter-paths.pointerMoveFunc: ', cursorPos, transPos);
                if (!this.local || !Object.keys(this.local).length) {
                    let local = this.getTemplate(transPos);
                    this.local = local;
                }

                let p0 = this.local.controlPoints[0],
                    p3 = this.getAnchor(transPos);

                // calculate the distance to control points
                let radius = p0.distance(p3) / Math.sqrt(2),
                    d = radius * 0.6641; // clothoids factor

                let m1 = 1,
                    m2 = 1;
                if (Point.getAngleDelta(transPos.angle(p0), p0.a) > 90) m1 = -1;
                if (Point.getAngleDelta(transPos.angle(p3), p3.a) > 90) m2 = -1;

                let isStraight = false;

                let straightStartAngle = p0.a === null || isStraight || this.isAltFnActive;
                let straightEndAngle = p3.a === null || isStraight || this.isAltFnActive;

                let p1 = straightStartAngle ? p0 : new Point(d * m1, 0).rotate(p0, p0.a).round(),
                    p2A = straightEndAngle ? p3 : new Point(d * -m2, 0).rotate(p3, p3.a).round(),
                    p2B = straightEndAngle ? p3 : new Point(d * +m2, 0).rotate(p3, p3.a).round();

                let bA = [p0, p1, p2A, p3],
                    bB = [p0, p1, p2B, p3];

                // select the shortest path to avoid extreme angle changes
                let p2 = this.getLength(bA) < this.getLength(bB) ? p2A : p2B;

                this.local.controlPoints = [p0, p1, p2, p3];

                // console.warn('p-painter-paths.pointerMoveFunc: p0, p3 => ', JSON.stringify(p0), JSON.stringify(p3));
                // console.warn('p-painter-paths.pointerMoveFunc: p1, p2 -> ', JSON.stringify(p1), JSON.stringify(p2));
                // console.warn('p-painter-paths.pointerMoveFunc: ', JSON.stringify(this.local.controlPoints));

                // console.warn('p-painter-paths.pointerMoveFunc: 1111 ', Point.equals(p0, p3));
                let idx = this.localList.findIndex(v => v.id === this.local.id);
                if (!Point.equals(p0, p3)) {
                    if (idx >= 0) {
                        this.localList[idx].controlPoints = this.local.controlPoints;
                    } else {
                        this.localList.push(this.local);
                    }
                    // this.local = {};
                } else {
                    // this.localList.splice(idx, 1);
                    // this.local = {};
                }
            }
            return true;
        },
        pointerUpFunc(cursorPos, transPos) {
            if (this.pointerButton === 2) return;
            // Create an editable Bezier.
            if (!this.isDrawPathsActive && this.bezier && this.bezierPointIdx > -1) {
                this.bezierPointIdx = -1;
                return;
            }

            // console.log('pointerFunc --> up: ', cursorPos, transPos, this.local);
            if (this.isMultiSelectActive) {
                this.multiSelect(cursorPos, transPos);
                return;
            }
            if (!this.isDrawPathsActive) return;

            this.painting = false;
            /*
             * Q: cp 'length' of undefined
             * A: 1、this.local is undefined or {}, 2、this.local.controlPoints is undefined, 3、cp.length is undefined
             * R: if you want to paint paths, but don't paint.
             *    when you click the left key once, this.local is not found in pointMoveFunc(this.local is {})
             *               -- mod by ouyang on 2020.07.20(Question, Answer, Reason)
             */
            // console.error('test: ', this.local);
            if (!this.local || !Object.keys(this.local).length) return; // 1、this.local !== undefined or {}
            let cp = this.local.controlPoints ? this.local.controlPoints : []; // 2、controlPoints !== undefined
            if ((cp && cp.length && cp.length < 4) || Point.equals(cp[0], cp[3])) return (this.local = null); // 3、

            let steps = 1 / (this.getLength(cp) / 100);

            let p1A = Point.getBezierPoint(steps, cp),
                p2A = Point.getBezierPoint(1 - steps, cp);

            cp[0].a = cp[0].h = cp[0].a === null ? Math.round(p1A.angle(cp[0])) : cp[0].a;
            cp[3].a = cp[3].h = cp[3].a === null ? Math.round(cp[3].angle(p2A)) : cp[3].a;

            if (this.isStraight(cp)) cp.splice(1, 2);

            let idx = this.localList.findIndex(v => v.id === this.local.id);
            if (idx >= 0) {
                this.localList[idx] = this.local;
            } else {
                this.localList.push(this.local);
            }
            this.local = null;
            // toolbar.altFnCallback = null;
        },
        pointerLeaveFunc() {
            // console.log('pointerFunc --> leave: ', this.local);
            this.painting = false;
        },
        multiSelect(cursorPos, transPos) {
            /*
             * Q: 'x' of null
             * A: pos0 or pos1 of null
             *              -- mod by ouyang on 2020.07.21
             */
            if (!cursorPos || !transPos || !this.startPoint || !this.isMultiSelectActive) return;
            let pos0 = this.startPoint,
                pos1 = transPos,
                p0 = {},
                p1 = {};
            this.startPoint = null;
            // console.warn('p-painter-paths.pointerUpFunc: ', pos0, pos1);

            p0.x = Math.min(pos0.x, pos1.x);
            p0.y = Math.min(pos0.y, pos1.y);
            p1.x = Math.max(pos0.x, pos1.x);
            p1.y = Math.max(pos0.y, pos1.y);
            let selects = [];
            this.paths.forEach(c => {
                if (!c.controlPoints || !c.controlPoints.length) return;
                let tag = true;
                c.controlPoints.some(p => {
                    if (p.x < p0.x || p.x > p1.x || p.y < p0.y || p.y > p1.y) {
                        tag = false;
                        return true;
                    }
                });
                if (tag) selects.push(c.id);
            });
            // console.warn('p-painter-paths.pointerUpFunc: ', p0, p1, selects);
            if (selects && selects.length) this.$store.commit(enums.DESELECT_PATHS_ALL, selects);
            this.$emit('pathsChange', selects);
        },

        getAnchor(transPos) {
            let gridSpacing = this.$parent.getGridSpacing(),
                anchor = new Point();
            anchor.x = Math.round(transPos.x / gridSpacing) * gridSpacing;
            anchor.y = Math.round(transPos.y / gridSpacing) * gridSpacing;
            anchor.a = null;

            // console.warn('---> ', transPos, anchor);
            let minDistance = transPos.distance(anchor),
                distance,
                p;
            let pool = this.localList.slice();
            pool.push.apply(pool, this.paths);

            for (let i = 0; i < pool.length; i++) {
                let s = pool[i];

                p = s.controlPoints ? s.controlPoints[0] : {};
                distance = transPos.distance(p);
                if (distance < minDistance + 100) {
                    // 100 mm tolerance
                    anchor = new Point(p.x, p.y);
                    anchor.a = p.a;
                    minDistance = distance;
                    this.template = s;
                }
                p = s.controlPoints ? s.controlPoints[s.controlPoints.length - 1] : {};
                distance = transPos.distance(p);
                if (distance < minDistance + 100) {
                    // 100 mm tolerance
                    anchor = new Point(p.x, p.y);
                    anchor.a = p.a;
                    minDistance = distance;
                    this.template = s;
                }
            }
            return anchor;
        },
        getLength(cp) {
            let length = 0;
            for (let i = 1; i < cp.length; i++) length += cp[i - 1].distance(cp[i]);
            return length;
        },
        isStraight(cp) {
            if (Point.equals(cp[0], cp[1]) && Point.equals(cp[2], cp[3])) return true;
            let angle = Math.round(cp[3].angle(cp[0]));
            return cp[0].a === angle && cp[3].a === angle;
        },
        getTemplate(transPos) {
            let startPoint = this.getAnchor(transPos),
                sess = Utils.getSessionStorage('CREDENTIALS'),
                username = sess.get('username');
            // if (!this.template) {
            if (!this.$root.resources || !this.paths || !this.paths.length) {
                this.template = {};
                this.template.width = 100;
                this.template.color = 'rgba(0,0,0,0.25)';
                this.template.labelColor = 'rgba(175,175,175,0.75)';
                this.template.labelOffset = new Point(0, 0);
            } else {
                let index = this.paths.length;
                // while (index--) {
                //     if (!this.template) this.template = this.paths[index];
                //     else if (this.paths[index].responsibility === REST.username) {
                //         this.template = this.paths[index];
                //         break;
                //     }
                // }
                this.template = this.paths[index - 1];
            }
            // }
            // this.template = {};
            // this.template.width = 500;
            // this.template.color = 'rgba(0,0,0,0.25)';
            // this.template.labelColor = 'rgba(175,175,175,0.75)';
            // this.template.labelOffset = new Point(0, 0);
            delete this.template.initTime;

            let s = JSON.parse(JSON.stringify(this.template));

            s.idx = this.localList.length ? this.localList[this.localList.length - 1].idx + 1 : 0;
            s.id = 'temp_' + s.idx;
            s.controlPoints = [startPoint];
            s.heading = 'FORWARD';
            s.label = '';
            s.responsibility = username;
            s.properties = {};

            return s;
        },
        undo() {
            console.info('p-painter-paths.undo ...', this.local, this.localList);
            if (this.local && Object.keys(this.local).length) {
                this.local = null;
            } else if (this.localList && this.localList.length) {
                this.localList.pop();
            }
            if (!this.localList.length) this.delayRenderNum = 0;
        },

        // Cache for offscrren render. 
        setCtxCache(key, canvas) {
            if (!this.ctxCache) this.ctxCache = { key: '', val: null };
            if (this.ctxCache.key !== key) {
                this.ctxCache.key = key;
                this.ctxCache.val = canvas;
            }
            return this.ctxCache.val;
        },
        getCtxCache(key) {
            if (this.ctxCache && this.ctxCache.key === key) return this.ctxCache.val;
            return null;
        },
        editChanged(res) {
            this.editStatus = res;
        }
    }
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<!-- <style scoped lang="scss"> -->
<style lang="scss">
.p-painter-paths {
    width: 100%;
    height: 100%;
}
</style>
